/*
 * Author: Anand Gorantala
 * Version: 2.0
 * Changed name to DownloaderThread, added instance variables
 * The constructor now sets all the values and spawns a new thread
 * 
 * Version: 1.0 
 * Description: Thread to manage a download
 * Requires: Target IP Address, Port, FileName
 */
package gmu.swe.project.peer;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Observable;
import java.util.UUID;


import gmu.swe.project.query.*;

public class DownloaderThread extends Observable implements Runnable  {
	private Socket socket = null;
	String trackerInfoIP, ip, fileName, sharedDirectory;
	int targetPort, trackerInfoPort, bytesRead, status, retries = 0, peerNo = 0, filesize=0, downloaded=0, buffer = 102400;
	BufferedReader in = null;
	PrintWriter out = null;
	InputStream is = null;
	BufferedInputStream bis = null;
	RandomAccessFile ras = null;
	public static final String statuses[] = {"Downloading",
        "Paused", "Complete", "Cancelled", "Error"};
	int downloading = 0, paused = 1, complete = 2, cancelled = 3, error = 4;
    private QueryResultSet peerList = new QueryResultSet();
    private ArrayList<QueryResult> queryresultarray;
    private QueryResult currentPeer = new QueryResult();
    private static ObjectOutputStream oos = null;
	private static ObjectInputStream ois = null; 
	private static Socket peerSocket = null;
	byte [] mybytearray  = new byte [102400];
    
	public DownloaderThread(String trackerInfoIP, int trackerInfoPort, String fileName, String sharedDirectory){
		this.sharedDirectory = sharedDirectory;
		this.trackerInfoIP = trackerInfoIP;
		this.trackerInfoPort = trackerInfoPort;
		this.fileName = fileName;
		status = 0;
		
		download();
	}
	

	public String getIP() {
        return ip;
    }
    
	public String getFileName() {
        return fileName;
    }
    
	public int getSize() {
        return filesize;
    }
    
    public float getProgress() {
        return ((float) downloaded / filesize) * 100;
    }
    
    public int getStatus() {
        return status;
    }
    
    
    public void resume() {
	        status = downloading;
	        stateChanged();
	        download();
    }
    
    public void pause() {
    	System.out.println("Paused()");
        status = paused;
        stateChanged();
    }
    
    public void cancel() {
    	System.out.println("Cancelled()");
        status = cancelled;
        stateChanged();
    }
    
    private void error() {
    	System.out.println("Error()");
        status = error;
        stateChanged();
    }
    
    private void sortResults() {
    	if(queryresultarray.size() > 1) {
	    	QueryResult temp = new QueryResult();
	    	temp = queryresultarray.get(0);
	    	for(int i=1;i<queryresultarray.size();i++){
	    		if(temp.getWorkload() > queryresultarray.get(i).getWorkload()) {
	    			temp = queryresultarray.get(i);
	    			queryresultarray.set(i, queryresultarray.get(i-1));
	    			queryresultarray.set(i-1, temp);
	    		}
	    	}
    	}
    	
    }
    
    private void queryTracker() {
    	
    		System.out.println("Query Tracker()");
    		try {
    			// send the server a query

    			String queryFile = this.fileName;
    			
    			System.out.println("Searching for file: " + queryFile);
    			// Add the query to a message. ID will be returned in the ACK
    			Query query = new Query();
    			query.setQuery(queryFile);
    			query.setId(UUID.randomUUID().toString());
    			query.setType(Query.PEER_QUERY);

    			// send the Query to the server
    			
    			peerSocket = new Socket(trackerInfoIP, trackerInfoPort);
    			DownloaderThread.oos = new ObjectOutputStream(peerSocket.getOutputStream());
    			DownloaderThread.ois = new ObjectInputStream(peerSocket.getInputStream());
    			
    			System.out.println("Sending Query to Server.");
    			DownloaderThread.oos.writeObject(query);
    			
    			// wait for the response
    			System.out.println("Waiting for response.");
    			peerList = (QueryResultSet) DownloaderThread.ois.readObject();
    			
    			queryresultarray = peerList.getQueryResultSet();
    			System.out.println("No of results "+queryresultarray.size());
    			
    			sortResults();
    			peerNo=0;
    			
    			peerSocket.close();
    			oos.close();
    			ois.close();
    		} catch (IOException ioex) {
    			System.out.println(ioex.toString());
    		} catch (ClassNotFoundException cnfex) {
    			System.out.println(cnfex.toString());
    		}
    		
			
    }
	private void download(){
		System.out.println("Download()");
				
		Thread thread = new Thread(this);
		thread.start();
		
	}
	

	private void closeSocket(){
	    if(socket !=null) {
	    System.out.println("Closing all connections to socket..."+socket);
		    try {
		    	is.close();
			    ras.close();
			    bis.close();
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	    }
	}
	
	private void stateChanged() {
		setChanged();
		notifyObservers();
	}
	
	public void run() {
		queryTracker();
		while(peerNo < queryresultarray.size() && status==downloading) {
			this.currentPeer = queryresultarray.get(peerNo);
			this.ip = currentPeer.getPeer();
			this.targetPort = currentPeer.getPort().intValue();
			
			closeSocket();
			
			try {
				socket = new Socket(this.ip,this.targetPort);
				System.out.println("Successfully connected to peer..."+socket);
			
		// Send the file name	
				out = new PrintWriter(socket.getOutputStream(), true);
				out.println(this.fileName);
			    System.out.println("Sent request for file: "+fileName);
				// Get the file size
				  in = new BufferedReader(new 
					        InputStreamReader(socket.getInputStream()));
				  filesize = Integer.parseInt(in.readLine());
			      System.out.println("Filesize is "+filesize);
				  
				  
			// Check if file exists on the target machine
				  if(filesize != 0) {  	    	
				    	
					File file = new File(sharedDirectory+File.separator+"copy-"+fileName);
					if(file.exists())
						this.downloaded = (int) file.length();
		
					out.println(this.downloaded);
			// Start the file download	
						mybytearray  = new byte [this.buffer];
					    is = socket.getInputStream();
					    bis = new BufferedInputStream(is);
					    ras = new  RandomAccessFile(file, "rw");
					    ras.seek(downloaded);
				
					    do{
					    	 if((filesize - downloaded) < buffer){
					    		  buffer = filesize - downloaded;
					    	  }
					    	
					       bytesRead = bis.read(mybytearray, 0, buffer);
						   if(bytesRead > 0) ras.write(mybytearray, 0 , bytesRead);
						   downloaded += bytesRead;
						   stateChanged();
					    } while (bytesRead > 0 && status==downloading);
					    
					    if(downloaded==filesize) {
						    status = complete;
						    stateChanged();
			
						    
						    closeSocket();
					    }
					
				
				} else {
				
			// Close the socket
					closeSocket();				
				}
		  
			}catch (Exception e) {
				peerNo++;
				e.printStackTrace();
			}
			
		} // while loop
		
		if(downloaded!=filesize && status==downloading)
		error();
	}
	
}

